#include "qkeymanager.h"

#include "Shared/Emulator.h"
#include "Shared/KeyDefinitions.h"
#include "Shared/KeyManager.h"
#include "Utilities/FolderUtilities.h"

#include <QKeyEvent>
#include <QWidget>

#include <QDebug>

const static KeyDefinition qDefaultKeyDefinitions[] = {
    {"Esc", 9},
    {"Backspace", 22},
    {"Enter", 36},
    {"Space", 65},
    {"Caps Lock", 66},
    {"Left Shift", 50},
    {"Right Shift", 62},
    {"Left Ctrl", 37},
    {"Left Alt", 64},
    {"Volume Mute", 121},
    {"Volume Down", 122},
    {"Volume Up", 123},
    {"Next Track", 171},
    {"Previous Track", 173},
    {"Stop", 174},
    {"Play/Pause", 172},
    {"Start Mail", 163},
    {"Select Media", 148},
    {"Start Application 1", 156},
    {"Left Win", 133},
    {"Right Win", 134},
    {"Apps", 135},
    {"Print", 107},
    {"Page Up", 112},
    {"Page Down", 117},
    {"Left Arrow", 113},
    {"Right Arrow", 114},
    {"Up Arrow", 111},
    {"Down Arrow", 116},
    {"Insert", 118},
    {"Delete", 119},
    {"Home", 110},
    {"End", 115},
    {";", 47},
    {"=", 21},
    {",", 59},
    {"-", 20},
    {".", 60},
    {"/", 61},
    {"`", 49},
    {"[", 34},
    {"]", 35},
    {"\\", 51},
    {"'", 48},
    {"A", 38},
    {"B", 56},
    {"C", 54},
    {"D", 40},
    {"E", 26},
    {"F", 41},
    {"G", 42},
    {"H", 43},
    {"I", 31},
    {"J", 44},
    {"K", 45},
    {"L", 46},
    {"M", 58},
    {"N", 57},
    {"O", 32},
    {"P", 33},
    {"Q", 24},
    {"R", 27},
    {"S", 39},
    {"T", 28},
    {"U", 30},
    {"V", 55},
    {"W", 25},
    {"X", 53},
    {"Y", 29},
    {"Z", 52},
    {"0", 19},
    {"1", 10},
    {"2", 11},
    {"3", 12},
    {"4", 13},
    {"5", 14},
    {"6", 15},
    {"7", 16},
    {"8", 17},
    {"9", 18},
    {"F1", 67},
    {"F2", 68},
    {"F3", 69},
    {"F4", 70},
    {"F5", 71},
    {"F6", 72},
    {"F7", 73},
    {"F8", 74},
    {"F9", 75},
    {"F10", 76},
    {"Num Lock", 77},
    {"Scroll Lock", 78},
};

QKeyManager::QKeyManager(Emulator *emu, QWidget *parent)
    : m_emulator(emu)
    , m_widget(parent)
{
    ResetKeyState();

    if (!InitializeKeyDefinitions())
        m_keyDefinitions = KeyDefinition::GetSharedKeyDefinitions();

#ifdef _WIN32
    // Init XInput buttons
    vector<string> buttonNames = {
        "Up", "Down", "Left", "Right",
        "Start", "Select",
        "L3", "R3", "L1", "R1",
        "?", "?",
        "A", "B", "X", "Y",
        "L2", "R2",
        "RT Up", "RT Down", "RT Left", "RT Right",
        "LT Up", "LT Down", "LT Left", "LT Right",
        "LT Y", "LT X", "RT Y", "RT X"};
#else
    vector<string> buttonNames = {
        "A", "B", "C", "X", "Y", "Z", "L1", "R1", "L2", "R2", "Select", "Start", "L3", "R3",
        "X+", "X-", "Y+", "Y-", "Z+", "Z-",
        "X2+", "X2-", "Y2+", "Y2-", "Z2+", "Z2-",
        "Right", "Left", "Down", "Up",
        "Right 2", "Left 2", "Down 2", "Up 2",
        "Right 3", "Left 3", "Down 3", "Up 3",
        "Right 4", "Left 4", "Down 4", "Up 4",
        "Trigger", "Thumb", "Thumb2", "Top", "Top2",
        "Pinkie", "Base", "Base2", "Base3", "Base4",
        "Base5", "Base6", "Dead",
        "Y2", "X2", "Y3", "X3", "Z2", "Z3"};
#endif

#ifdef _WIN32
    for (int i = 0; i < 4; i++) {
        for (int j = 0; j < (int)buttonNames.size(); j++) {
            m_keyDefinitions.push_back({"Pad" + std::to_string(i + 1) + " " + buttonNames[j], (uint32_t)(BaseGamepadIndex + i * 0x100 + j + 1)});
        }
    }
#else
    for (int i = 0; i < 20; i++) {
        for (int j = 0; j < (int)buttonNames.size(); j++) {
            m_keyDefinitions.push_back({"Pad" + std::to_string(i + 1) + " " + buttonNames[j], (uint32_t)(BaseGamepadIndex + i * 0x100 + j)});
        }
    }
#endif

    for (KeyDefinition &keyDef : m_keyDefinitions) {
        m_keyNames[keyDef.keyCode] = keyDef.name;
        m_keyCodes[keyDef.name] = keyDef.keyCode;
    }

    m_disableAllKeys = false;

    if (m_widget) {
        m_widget->installEventFilter(this);
    }

#ifndef _WIN32
    CheckForGamepads(true);

    m_disableAllKeys = false;
    m_stopUpdateDeviceThread = false;
#endif
    StartUpdateDeviceThread();
    KeyManager::SetSettings(m_emulator->GetSettings());
    KeyManager::RegisterKeyManager(this);
}

QKeyManager::~QKeyManager()
{
    m_stopUpdateDeviceThread = true;
    m_stopSignal.Signal();
    m_updateDeviceThread.join();
}
void QKeyManager::RefreshState()
{
#ifdef _WIN32
    if (!m_xInput)
        return;

    m_xInput->RefreshState();
#endif
}

void QKeyManager::UpdateDevices()
{
#ifdef _WIN32
    if (!m_xInput)
        return;

    m_xInput->UpdateDeviceList();
#endif
}

bool QKeyManager::IsMouseButtonPressed(MouseButton button)
{
#ifdef _WIN32
    return m_keyState[BaseMouseButtonIndex + static_cast<int>(button)];
#else
    return m_keyState[BaseMouseButtonIndex + static_cast<int>(button)] = false;
#endif
}

bool QKeyManager::IsKeyPressed(uint16_t keyCode)
{
    if (m_disableAllKeys || keyCode == 0) {
        return false;
    }

    if (keyCode >= BaseGamepadIndex) {
#ifdef _WIN32
        if (!m_xInput)
            return false;

        // XInput key
        uint8_t gamepadPort = (keyCode - BaseGamepadIndex) / 0x100;
        uint8_t gamepadButton = (keyCode - BaseGamepadIndex) % 0x100;
        return m_xInput->IsPressed(gamepadPort, gamepadButton);
#else
        uint8_t gamepadPort = (keyCode - BaseGamepadIndex) / 0x100;
        uint8_t gamepadButton = (keyCode - BaseGamepadIndex) % 0x100;
        if (m_controllers.size() > gamepadPort) {
            return m_controllers[gamepadPort]->IsButtonPressed(gamepadButton);
        }
#endif
    } else if (keyCode < 0x205) {
        // Check if the key is pressed
        return m_keyState[keyCode] != 0;
    }
    return false;
}

optional<int16_t> QKeyManager::GetAxisPosition(uint16_t keyCode)
{
    if (keyCode >= BaseGamepadIndex) {
#ifdef _WIN32
        if (!m_xInput)
            return std::nullopt;
#endif
        uint8_t port = (keyCode - BaseGamepadIndex) / 0x100;
        uint8_t button = (keyCode - BaseGamepadIndex) % 0x100;
#ifdef _WIN32
        return m_xInput->GetAxisPosition(port, button);
#else
        return m_controllers[port]->GetAxisPosition(button);
#endif
    }
    return std::nullopt;
}

vector<uint16_t> QKeyManager::GetPressedKeys()
{
    vector<uint16_t> pressedKeys;
#ifdef _WIN32
    if (!m_xInput)
        return pressedKeys;

    m_xInput->RefreshState();
    for (int i = 0; i < XUSER_MAX_COUNT; i++) {
        for (int j = 1; j <= 26; j++) {
            if (m_xInput->IsPressed(i, j)) {
                pressedKeys.push_back(BaseGamepadIndex + i * 0x100 + j);
            }
        }
    }
#else
    for (size_t i = 0; i < m_controllers.size(); i++) {
        for (int j = 0; j <= 54; j++) {
            if (m_controllers[i]->IsButtonPressed(j)) {
                pressedKeys.push_back(BaseGamepadIndex + i * 0x100 + j);
            }
        }
    }
#endif

    for (int i = 0; i < 0x205; i++) {
        if (m_keyState[i]) {
            pressedKeys.push_back(i);
        }
    }
    return pressedKeys;
}

string QKeyManager::GetKeyName(uint16_t keyCode)
{
    auto keyDef = m_keyNames.find(keyCode);
    if (keyDef != m_keyNames.end()) {
        return keyDef->second;
    }
    return "";
}

uint16_t QKeyManager::GetKeyCode(string keyName)
{
    auto keyDef = m_keyCodes.find(keyName);
    if (keyDef != m_keyCodes.end()) {
        return keyDef->second;
    }
    return 0;
}

bool QKeyManager::SetKeyState(uint16_t scanCode, bool state)
{
    if (scanCode < 0x205 && m_keyState[scanCode] != state) {
        m_keyState[scanCode] = state;
        return true;
    }
    return false;
}

void QKeyManager::ResetKeyState()
{
    memset(m_keyState, 0, sizeof(m_keyState));
}

void QKeyManager::SetDisabled(bool disabled)
{
    m_disableAllKeys = disabled;
}

void QKeyManager::SetForceFeedback(uint16_t magnitudeRight, uint16_t magnitudeLeft)
{
#ifdef _WIN32
    m_xInput->SetForceFeedback(magnitudeRight, magnitudeLeft);
#else
    for (auto &controller : m_controllers) {
        controller->SetForceFeedback(magnitudeRight, magnitudeLeft);
    }
#endif
}

bool QKeyManager::InitializeKeyDefinitions()
{
    m_keyDefinitions.clear();

    for (const KeyDefinition &def : qDefaultKeyDefinitions) {
        m_keyDefinitions.push_back(def);
    }

    if (m_keyDefinitions.empty()) {
        qDebug() << "No key definitions found!";
        return false;
    }

    return true;
}

void QKeyManager::StartUpdateDeviceThread()
{
#ifndef _WIN32
    m_updateDeviceThread = std::thread([=]() {
        while (!m_stopUpdateDeviceThread) {
            // Check for newly plugged in controllers every 5 secs
            vector<shared_ptr<LinuxGameController>> controllersToAdd;
            vector<int> indexesToRemove;
            for (int i = m_controllers.size() - 1; i >= 0; i--) {
                if (m_controllers[i]->IsDisconnected()) {
                    indexesToRemove.push_back(i);
                }
            }

            CheckForGamepads(false);

            if (!indexesToRemove.empty() || !controllersToAdd.empty()) {
                m_emulator->Pause();
                for (int index : indexesToRemove) {
                    m_controllers.erase(m_controllers.begin() + index);
                }
                for (std::shared_ptr<LinuxGameController> controller : controllersToAdd) {
                    m_controllers.push_back(controller);
                }
                m_emulator->Resume();
            }

            m_stopSignal.Wait(5000);
        }
    });
#else
    m_updateDeviceThread = std::thread([=]() {
        m_xInput.reset(new XInputManager(m_emulator));

        while (!m_stopUpdateDeviceThread) {
            // Check for newly plugged in XInput controllers every 5 secs
            // Do not check for DirectInput controllers because this takes more time and sometimes causes issues/freezes
            if (m_xInput->NeedToUpdate()) {
                m_xInput->UpdateDeviceList();
            }
            m_stopSignal.Wait(5000);
        }
    });
#endif
}

#ifndef _WIN32
void QKeyManager::CheckForGamepads(bool logInformation)
{
    vector<int> connectedIDs;
    for (int i = m_controllers.size() - 1; i >= 0; i--) {
        if (!m_controllers[i]->IsDisconnected()) {
            connectedIDs.push_back(m_controllers[i]->GetDeviceID());
        }
    }

    vector<string> files = FolderUtilities::GetFilesInFolder("/dev/input/", {}, false);
    for (size_t i = 0; i < files.size(); i++) {
        string filename = FolderUtilities::GetFilename(files[i], false);
        if (filename.find("event", 0) == 0) {
            int deviceId = 0;
            try {
                deviceId = std::stoi(filename.substr(5));
            } catch (std::exception e) {
                continue;
            }
            if (std::find(connectedIDs.begin(), connectedIDs.end(), deviceId) == connectedIDs.end()) {
                std::shared_ptr<LinuxGameController> controller = LinuxGameController::GetController(m_emulator, deviceId, logInformation);
                if (controller) { 
                    m_controllers.push_back(controller);
                }
            }
        }
    }
}
#endif // _WIN32

bool QKeyManager::eventFilter(QObject *obj, QEvent *event)
{
    if (m_widget && obj == m_widget) {
        if (event->type() == QEvent::KeyPress || event->type() == QEvent::KeyRelease) {
            QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
#ifdef _WIN32
            uint32_t keyCode = keyEvent->nativeScanCode() + 8;
#else
            uint32_t keyCode = keyEvent->nativeScanCode();
#endif
            if (event->type() == QEvent::KeyPress) {
                SetKeyState(keyCode, true);
            } else {
                SetKeyState(keyCode, false);
            }
            return QObject::eventFilter(obj, event);
        }
    }
    return QObject::eventFilter(obj, event);
}